package beefs.highlevel;

import static org.junit.Assert.assertFalse;

import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

import org.designwizard.design.ClassNode;
import org.designwizard.design.MethodNode;
import org.designwizard.design.PackageNode;
import org.designwizard.exception.InexistentEntityException;
import org.designwizard.main.DesignWizard;
import org.junit.Test;

import beefs.common.Mappings;

public class HighLevelTests {

	private static final String JAVA = "java";

	private DesignWizard dw;

	public HighLevelTests() {
		try {
			this.dw = new DesignWizard("resources/allclasses");
		} catch (IOException e) {}
	}

	@Test
	public void fSDesignRules() throws InexistentEntityException {
		System.out.println("---" + Mappings.FS + " violations");
		
		PackageNode fsPackage = this.dw.getPackage(Mappings.FS);
		PackageNode dsPackage = this.dw.getPackage(Mappings.DS);

		// FS must not access DS
		Set<PackageNode> calleePackages = fsPackage.getCalleePackages();
		boolean fail = false;
		for (PackageNode pack: calleePackages)
			if (pack.equals(dsPackage)) {
				fail = true;
				System.out.println(Mappings.FS + " is_acessing " + dsPackage.getName());
			}

		Set<ClassNode> calleeClasses = fsPackage.getCalleeClasses();

		Set<String> allowed = new HashSet<String>();
		allowed.add(Mappings.COMMONS);
		allowed.add(JAVA);
		allowed.add(Mappings.FS);
		allowed.add(Mappings.DST_FACADE_CLASS);


		for (ClassNode c : calleeClasses) {
			if (!allowed(c.getName(), allowed)) {
				fail = true;
				System.out.println(Mappings.FS + " is_acessing " + c.getName());
			}
		}

		System.out.println();
		assertFalse(fail);
	}

	@Test
	public void dSTDesignRules() throws InexistentEntityException {
		System.out.println("---" + Mappings.DST + " violations");

		PackageNode dstPackage = this.dw.getPackage(Mappings.DST);
		PackageNode fsPackage = this.dw.getPackage(Mappings.FS);
		Set<MethodNode> methodsOfDST = dstPackage.getAllMethods(); 
		Set<PackageNode> calleePackages = dstPackage.getCalleePackages();

		boolean fail = false;
		for (PackageNode pack: calleePackages)
			if (pack.equals(fsPackage)) {
				fail = true;
				System.out.println(Mappings.DST + " is_acessing " + fsPackage.getName());
			}

		ClassNode dsOSD_Class = this.dw.getClass(Mappings.DS_OSD_CLASS);
		ClassNode dsSTO_Class = this.dw.getClass(Mappings.DS_STO_CLASS);

		Set<String> allowed = new HashSet<String>();
		allowed.add(Mappings.DST);
		allowed.add(JAVA);
		allowed.add(Mappings.COMMONS);
		allowed.add(dsOSD_Class.getName());
		allowed.add(dsSTO_Class.getName());


		for (MethodNode method : methodsOfDST) {

			Set<MethodNode> calleeMethods = method.getCalleeMethods();

			for (MethodNode callee : calleeMethods) {

				if (!allowed(callee.getName(), allowed)) {
					fail = true;
					System.out.println(method.getName() +" is_accessing " + callee.getName());
				}

			}
		}
		System.out.println();
		assertFalse(fail);
	}

	@Test
	public void dSDesignRules() throws InexistentEntityException {
		System.out.println("---" + Mappings.DS + " violations");
		PackageNode dsPackage = this.dw.getPackage(Mappings.DS);
		Set<PackageNode> calleePackages = dsPackage.getCalleePackages();

		Set<String> allowed = new HashSet<String>();
		allowed.add(Mappings.DS);
		allowed.add(JAVA);
		allowed.add(Mappings.COMMONS);

		System.out.println();
		boolean fail = false;
		for (PackageNode p : calleePackages)
			if (!allowed(p.getName(), allowed)) {
				fail = true;
				System.out.println(dsPackage.getName() +" is_accessing " + p.getName());
			}
		
		System.out.println();
		assertFalse(fail);
		
	}

	@Test
	public void fuseAdaptorDesignRules() throws InexistentEntityException {
		
		System.out.println("---" + Mappings.FUSE_ADAPTOR_CLASS + " violations");
		
		ClassNode fuseAdaptor = this.dw.getClass(Mappings.FUSE_ADAPTOR_CLASS);
		Set<MethodNode> allMethodsFuseAdaptor = fuseAdaptor.getAllMethods();
		Set<String> allowedEntities = new HashSet<String>();
		allowedEntities.add(Mappings.FUSE_ADAPTOR_CLASS);

		boolean fail = false;
		// não pode ser acessado por ninguém, exceto ele mesmo
		for (MethodNode method : allMethodsFuseAdaptor) {
			Set<MethodNode> callerMethods = method.getCallerMethods();
		
			for (MethodNode caller : callerMethods)
				if(!allowed(caller.getName(), allowedEntities)) {
					fail = true;
					System.out.println(method.getName() +" is_accessed_by " + caller.getName());
				} 
		}
				
		
		ClassNode pathClass = this.dw.getClass(Mappings.PATH_CLASS);
		ClassNode fileSystemClass = this.dw.getClass(Mappings.FILE_SYSTEM_CLASS);
		ClassNode sbcClass = this.dw.getClass(Mappings.SBC_CLASS);

		// pode acessar Path, FS.FileSystem, SBC, Java, Logger, fuse, client
		allowedEntities = new HashSet<String>();
		allowedEntities.add(JAVA);
		allowedEntities.add(Mappings.COMMONS);
		allowedEntities.add(Mappings.CLIENT);
		allowedEntities.add(Mappings.FUSE_ADAPTOR_CLASS);
		allowedEntities.add("fuse");
		allowedEntities.add("org.slf4j.Logger");
		allowedEntities.add(pathClass.getName());
		allowedEntities.add(fileSystemClass.getName());
		allowedEntities.add(sbcClass.getName());

		for (MethodNode method : allMethodsFuseAdaptor) {
			Set<MethodNode> callerMethods = method.getCallerMethods();
			for (MethodNode caller : callerMethods)
				if (!caller.getName().startsWith(Mappings.FUSE_ADAPTOR_CLASS)){
					System.out.println(caller.getName() + " is_accessing " + method.getName());
					fail = true;
				}
			
		}
		
		System.out.println();
		assertFalse(fail);

	}

	@Test
	public void dokanAdaptorDesignRules() throws InexistentEntityException {
		boolean fail = false;

		System.out.println("---" + Mappings.DOKAN_ADAPTOR_CLASS+ " violations");

		ClassNode dokanAdaptor = this.dw.getClass(Mappings.DOKAN_ADAPTOR_CLASS);
		Set<MethodNode> dokanAdaptorMethods = dokanAdaptor.getAllMethods();

		// não pode ser chamado por ninguém
		for (MethodNode method : dokanAdaptorMethods) {

			Set<MethodNode> callerMethods = method.getCallerMethods();
			for (MethodNode caller : callerMethods) {
				if (!caller.getName().startsWith(Mappings.DOKAN_ADAPTOR_CLASS)) {
					fail = true;
					System.out.println(method.getName() + " is_accessed_by " + caller.getName());
				}
			}
		}

		Set<String> allowed = new HashSet<String>();
		allowed.add(Mappings.DOKAN_ADAPTOR_CLASS);
		ClassNode pathClass = this.dw.getClass(Mappings.PATH_CLASS);
		ClassNode fileSystemClass = this.dw.getClass(Mappings.FILE_SYSTEM_CLASS);
		ClassNode sbcClass = this.dw.getClass(Mappings.SBC_CLASS);
		allowed.add(pathClass.getName());
		allowed.add(fileSystemClass.getName());
		allowed.add(sbcClass.getName());
		allowed.add("org.slf4j.Logger");
		allowed.add(JAVA);
		allowed.add(Mappings.COMMONS);

		Set<MethodNode> calleeMethods = dokanAdaptor.getCalleeMethods();

		for (MethodNode callee : calleeMethods) {
			if (!allowed(dokanAdaptor.getName(), allowed)) {
				fail = true;
				System.out.println(dokanAdaptor.getName() + " is_accessing " + callee.getName());
			}
		}

		System.out.println();
		assertFalse(fail);

	}

	@Test
	// Commons não pode acessar ninguém.
	public void commonsDesignRules() throws InexistentEntityException {
		System.out.println("---" + Mappings.COMMONS + " violations");

		boolean fail = false;

		Set<PackageNode> allPackages = this.dw.getAllPackages();
		for (PackageNode p : allPackages) {

			if (p.getName().startsWith(Mappings.COMMONS)) {

				Set<PackageNode> calleePackages = p.getCalleePackages();

				for (PackageNode callee : calleePackages)
					if (callee.getName().startsWith(Mappings.ID) && !callee.getName().startsWith(Mappings.COMMONS)) {
						System.out.println(p.getName() + " is_accessing " + callee);
						fail = true;
					}

			}

		}

		System.out.println();
		assertFalse(fail);


	}

	@Test
	// Pacotes internal só podem ser acessados por pacotes do mesmo projeto.
	public void internalDesignRules() {
		System.out.println("---" + "internal violations");

		boolean fail = false;
		Set<MethodNode> allMethods = this.dw.getAllMethods();
		for(MethodNode method : allMethods) {

			if (method.getName().contains("internal")) {

				Set<MethodNode> callerMethods = method.getCallerMethods();
				for(MethodNode caller : callerMethods) {

					String allowed = method.getName().substring(0,method.getName().indexOf("internal")-1);

					if(!caller.getName().startsWith(allowed)) {
						System.out.println(method.getName() + " is_accessed_by " + caller.getName());
						fail = true;
					}

				}
			}

		}

		System.out.println();
		assertFalse(fail);


	}

	private static boolean allowed(String caller, Set<String> allowed) {
		for(String n : allowed) {
			if (caller.startsWith(n)) return true;
		}
		return false;
	}

}